Package g4p_controls

Source Code of g4p_controls.GEditableTextControl

/*
  Part of the GUI for Processing library
    http://www.lagers.org.uk/g4p/index.html
  http://gui4processing.googlecode.com/svn/trunk/

  Copyright (c) 2013 Peter Lager

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

package g4p_controls;

import g4p_controls.StyledString.TextLayoutHitInfo;
import g4p_controls.StyledString.TextLayoutInfo;

import java.awt.Font;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.geom.GeneralPath;
import java.util.LinkedList;

import processing.core.PApplet;
import processing.event.KeyEvent;

/**
*
* This class is the basis for the GTextField and GTextArea classes.
*
* @author Peter Lager
*
*/
public abstract class GEditableTextControl extends GTextBase {

  protected static float HORZ_SCROLL_RATE = 4f;
  protected static float VERT_SCROLL_RATE = 8;

  GTabManager tabManager = null;

  protected StyledString defaultText = null;
  // The width to break a line
  protected int wrapWidth = Integer.MAX_VALUE;

  // The typing area
  protected float tx,ty,th,tw;
  // Offset to display area
  protected float ptx, pty;
  // Caret position
  protected float caretX, caretY;

  protected boolean keepCursorInView = false;

  protected GeneralPath gpTextDisplayArea;

  // Used for identifying selection and cursor position
  protected TextLayoutHitInfo startTLHI = new TextLayoutHitInfo();
  protected TextLayoutHitInfo endTLHI = new TextLayoutHitInfo();

  // The scrollbars available
  protected final int scrollbarPolicy;
  protected boolean autoHide = false;
  protected GScrollbar hsb, vsb;

  protected GTimer caretFlasher;
  protected boolean showCaret = false;

  // Stuff to manage text selections
  protected int endChar = -1, startChar = -1, pos = endChar, nbr = 0, adjust = 0;
  protected boolean textChanged = false, newline = false, selectionChanged = false;

  /* Is the component enabled to generate mouse and keyboard events */
  boolean textEditEnabled = true;

  public GEditableTextControl(PApplet theApplet, float p0, float p1, float p2, float p3, int scrollbars) {
    super(theApplet, p0, p1, p2, p3);
    scrollbarPolicy = scrollbars;
    autoHide = ((scrollbars & SCROLLBARS_AUTOHIDE) == SCROLLBARS_AUTOHIDE);
    caretFlasher = new GTimer(theApplet, this, "flashCaret", 400);
    caretFlasher.start();
    opaque = true;
    cursorOver = TEXT;
  }

  /**
   * Give up focus but if the text is only made from spaces
   * then set it to null text. <br>
   * Fire focus events for the GTextField and GTextArea controls
   */
  protected void loseFocus(GAbstractControl grabber){
    // If this control has focus then Fire a lost focus event
    if(focusIsWith == this)
      fireEvent(this, GEvent.LOST_FOCUS);
    // Process mouse-over cursor
    if(cursorIsOver == this)
      cursorIsOver = null;
    focusIsWith = grabber;
    // If only blank text clear it out allowing default text (if any) to be displayed
    if(stext.length() > 0){
      int tl = stext.getPlainText().trim().length();
      if(tl == 0)
        stext = new StyledString("", wrapWidth);
    }
    keepCursorInView = true;
    bufferInvalid = true;
  }

  /**
   * Give the focus to this component but only after allowing the
   * current component with focus to release it gracefully. <br>
   * Always cancel the keyFocusIsWith irrespective of the component
   * type.
   * Fire focus events for the GTextField and GTextArea controls
   */
  protected void takeFocus(){
    // If focus is not yet with this control fire a gets focus event
    if(focusIsWith != this){
      // If the focus is with another control then tell
      // that control to lose focus
      if(focusIsWith != null)
        focusIsWith.loseFocus(this);
      fireEvent(this, GEvent.GETS_FOCUS);
    }
    focusIsWith = this;
  }

  /**
   * Determines whether this component is to have focus or not. <br>
   * Fire focus events for the GTextField and GTextArea controls
   * @param focus
   */
  public void setFocus(boolean focus){
    if(!focus){
      loseFocus(null);
      return;
    }
    // Make sure we have some text
    if(focusIsWith != this){
      dragging = false;
      if(stext == null || stext.length() == 0)
        stext = new StyledString(" ", wrapWidth);
//      text = stext.getPlainText();
      LinkedList<TextLayoutInfo> lines = stext.getLines(buffer.g2);
      startTLHI = new TextLayoutHitInfo(lines.getFirst(), null);
      startTLHI.thi = startTLHI.tli.layout.getNextLeftHit(1);

      endTLHI = new TextLayoutHitInfo(lines.getLast(), null);
      int lastChar = endTLHI.tli.layout.getCharacterCount();
      endTLHI.thi = startTLHI.tli.layout.getNextRightHit(lastChar-1);

      calculateCaretPos(endTLHI);
      bufferInvalid = true;
    }
    keepCursorInView = true;
    takeFocus();
  }

  /**
   * Set the default text for this control. If provided this text will be
   * displayed in italic whenever it is empty.
   * @param dtext
   */
  public void setDefaultText(String dtext){
    if(dtext == null || dtext.length() == 0)
      defaultText = null;
    else {
      defaultText = new StyledString(dtext, wrapWidth);
      defaultText.addAttribute(G4P.POSTURE, G4P.POSTURE_OBLIQUE);
    }
    bufferInvalid = true;
  }

  /**
   * Get the default text for this control
   * @return the default text without styling
   */
  public String getDefaultText(){
    return defaultText.getPlainText();
  }

  /**
   * Get the text in the control
   * @return the text without styling
   */
  public String getText(){
    return stext.getPlainText();
  }

  /**
   * Get the styled text in the control
   * @return the text with styling
   */
  public StyledString getStyledText(){
    return stext;
  }

  /**
   * Get the text that has been selected (highlighted) by the user. <br>
   * @return the selected text without styling
   */
  public String getSelectedText(){
    if(!hasSelection())
      return "";
    TextLayoutHitInfo startSelTLHI;
    TextLayoutHitInfo endSelTLHI;
    if(endTLHI.compareTo(startTLHI) == -1){
      startSelTLHI = endTLHI;
      endSelTLHI = startTLHI;
    }
    else {
      startSelTLHI = startTLHI;
      endSelTLHI = endTLHI;
    }
    int ss = startSelTLHI.tli.startCharIndex + startSelTLHI.thi.getInsertionIndex();
    int ee = endSelTLHI.tli.startCharIndex + endSelTLHI.thi.getInsertionIndex();
    String s = stext.getPlainText().substring(ss, ee);
    return s;
  }

  /**
   * If some text has been selected then set the style. If there is no selection then
   * the text is unchanged.
   *
   *
   * @param style
   */
  public void setSelectedTextStyle(TextAttribute style, Object value){
    if(!hasSelection())
      return;
    TextLayoutHitInfo startSelTLHI;
    TextLayoutHitInfo endSelTLHI;
    if(endTLHI.compareTo(startTLHI) == -1){
      startSelTLHI = endTLHI;
      endSelTLHI = startTLHI;
    }
    else {
      startSelTLHI = startTLHI;
      endSelTLHI = endTLHI;
    }
    int ss = startSelTLHI.tli.startCharIndex + startSelTLHI.thi.getInsertionIndex();
    int ee = endSelTLHI.tli.startCharIndex + endSelTLHI.thi.getInsertionIndex();
    stext.addAttribute(style, value, ss, ee);

    // We have modified the text style so the end of the selection may have
    // moved, so it needs to be recalculated. The start will be unaffected.
    stext.getLines(buffer.g2);
    endSelTLHI.tli = stext.getTLIforCharNo(ee);
    int cn = ee - endSelTLHI.tli.startCharIndex;
    if(cn == 0) // start of line
      endSelTLHI.thi = endSelTLHI.tli.layout.getNextLeftHit(1);
    else
      endSelTLHI.thi = endSelTLHI.tli.layout.getNextRightHit(cn-1);
    bufferInvalid = true;
  }
  /**
   * Clear any styles applied to the selected text.
   */
  public void clearStyle(){
    if(!hasSelection())
      return;
    TextLayoutHitInfo startSelTLHI;
    TextLayoutHitInfo endSelTLHI;
    if(endTLHI.compareTo(startTLHI) == -1){
      startSelTLHI = endTLHI;
      endSelTLHI = startTLHI;
    }
    else {
      startSelTLHI = startTLHI;
      endSelTLHI = endTLHI;
    }
    int ss = startSelTLHI.tli.startCharIndex + startSelTLHI.thi.getInsertionIndex();
    int ee = endSelTLHI.tli.startCharIndex + endSelTLHI.thi.getInsertionIndex();
    stext.clearAttributes(ss, ee);

    // We have modified the text style so the end of the selection may have
    // moved, so it needs to be recalculated. The start will be unaffected.
    stext.getLines(buffer.g2);
    endSelTLHI.tli = stext.getTLIforCharNo(ee);
    int cn = ee - endSelTLHI.tli.startCharIndex;
    if(cn == 0) // start of line
      endSelTLHI.thi = endSelTLHI.tli.layout.getNextLeftHit(1);
    else
      endSelTLHI.thi = endSelTLHI.tli.layout.getNextRightHit(cn-1);
    bufferInvalid = true;
  }

  /**
   * Set the font for this control.
   * @param font
   */
  public void setFont(Font font) {
    if(font != null && font != localFont && buffer != null){
      localFont = font;
      buffer.g2.setFont(localFont);
      stext.getLines(buffer.g2);
      ptx = pty = 0;
      setScrollbarValues(ptx, pty);
      bufferInvalid = true;
    }
  }

  /**
   * Used internally to set the scrollbar values as the text changes.
   *
   * @param sx
   * @param sy
   */
  void setScrollbarValues(float sx, float sy){
    if(vsb != null){
      float sTextHeight = stext.getTextAreaHeight();
      if(sTextHeight < th)
        vsb.setValue(0.0f, 1.0f);
      else
        vsb.setValue(sy/sTextHeight, th/sTextHeight);
    }
    // If needed update the horizontal scrollbar
    if(hsb != null){
      float sTextWidth = stext.getMaxLineLength();
      if(stext.getMaxLineLength() < tw)
        hsb.setValue(0,1);
      else
        hsb.setValue(sx/sTextWidth, tw/sTextWidth);
    }
  }

  /**
   * Move caret to home position
   * @param currPos the current position of the caret
   * @return true if caret moved else false
   */
  protected boolean moveCaretStartOfLine(TextLayoutHitInfo currPos){
    if(currPos.thi.getCharIndex() == 0)
      return false; // already at start of line
    currPos.thi = currPos.tli.layout.getNextLeftHit(1);
    return true;
  }

  /**
   * Move caret to the end of the line that has the current caret position
   * @param currPos the current position of the caret
   * @return true if caret moved else false
   */
  protected boolean moveCaretEndOfLine(TextLayoutHitInfo currPos){
    if(currPos.thi.getCharIndex() == currPos.tli.nbrChars - 1)
      return false; // already at end of line
    currPos.thi = currPos.tli.layout.getNextRightHit(currPos.tli.nbrChars - 1);
    return true;
  }

  /**
   * Move caret left by one character.
   * @param currPos the current position of the caret
   * @return true if caret moved else false
   */
  protected boolean moveCaretLeft(TextLayoutHitInfo currPos){
    TextHitInfo nthi = currPos.tli.layout.getNextLeftHit(currPos.thi);
    if(nthi == null){
      return false;
    }
    else {
      // Move the caret to the left of current position
      currPos.thi = nthi;
    }
    return true;
  }

  /**
   * Move caret right by one character.
   * @param currPos the current position of the caret
   * @return true if caret moved else false
   */
  protected boolean moveCaretRight(TextLayoutHitInfo currPos){
    TextHitInfo nthi = currPos.tli.layout.getNextRightHit(currPos.thi);
    if(nthi == null){
      return false;
    }
    else {
      currPos.thi = nthi;
    }
    return true;
  }

  public void setJustify(boolean justify){
    stext.setJustify(justify);
    bufferInvalid = true;
  }

  /**
   * Sets the local colour scheme for this control
   */
  public void setLocalColorScheme(int cs){
    super.setLocalColorScheme(cs);
    if(hsb != null)
      hsb.setLocalColorScheme(localColorScheme);
    if(vsb != null)
      vsb.setLocalColorScheme(localColorScheme);
  }

  /**
   * Find out if some text is selected (highlighted)
   * @return true if some text is selected else false
   */
  public boolean hasSelection(){
    return (startTLHI.tli != null && endTLHI.tli != null && startTLHI.compareTo(endTLHI) != 0)
  }

  /**
   * Calculate the caret (text insertion point)
   *
   * @param tlhi
   */
  protected void calculateCaretPos(TextLayoutHitInfo tlhi){
    float temp[] = tlhi.tli.layout.getCaretInfo(tlhi.thi);
    caretX = temp[0];   
    caretY = tlhi.tli.yPosInPara;
  }

  /**
   * Determines whether the text can be edited using the keyboard or mouse. It
   * still allows the text to be modified by the sketch code. <br>
   * If text editing is being disabled and the control has focus then it is forced
   * to give up that focus. <br>
   * This might be useful if you want to use a GTextArea control to display large
   * amounts of text that needs scrolling (so cannot use a GLabel) but must not
   * change e.g. a user instruction guide.
   *
   * @param enableTextEdit false to disable keyboard input
   */
  public void setTextEditEnabled(boolean enableTextEdit){
    // If we are disabling this then make sure it does not have focus
    if(enableTextEdit == false && focusIsWith == this){
      loseFocus(null);
    }
    enabled = enableTextEdit;
    textEditEnabled = enableTextEdit;
  }

  public void keyEvent(KeyEvent e) {
    if(!visible  || !enabled || !textEditEnabled || !available) return;
    if(focusIsWith == this && endTLHI != null){
      char keyChar = e.getKey();
      int keyCode = e.getKeyCode();
      int keyID = e.getAction();
      boolean shiftDown = e.isShiftDown();
      boolean ctrlDown = e.isControlDown();

      textChanged = false;
      newline = false;
      keepCursorInView = true;

      int startPos = pos, startNbr = nbr;

      // Get selection details
      endChar = endTLHI.tli.startCharIndex + endTLHI.thi.getInsertionIndex();
      startChar = (startTLHI != null) ? startTLHI.tli.startCharIndex + startTLHI.thi.getInsertionIndex() : endChar;
      pos = endChar;
      nbr = 0;
      adjust = 0;
      if(endChar != startChar){ // Have we some text selected?
        if(startChar < endChar){ // Forward selection
          pos = startChar; nbr = endChar - pos;
        }
        else if(startChar > endChar){ // Backward selection
          pos = endChar;  nbr = startChar - pos;
        }
      }
      if(startPos >= 0){
        if(startPos != pos || startNbr != nbr)
          fireEvent(this, GEvent.SELECTION_CHANGED);
      }

      if(keyID == KeyEvent.PRESS) {
        keyPressedProcess(keyCode, keyChar, shiftDown, ctrlDown);
        setScrollbarValues(ptx, pty);
      }
      else if(keyID == KeyEvent.TYPE ){ // && e.getKey()  != KeyEvent.CHAR_UNDEFINED && !ctrlDown){
        keyTypedProcess(keyCode, keyChar, shiftDown, ctrlDown);
        setScrollbarValues(ptx, pty);
      }
      if(textChanged){
        changeText();
        fireEvent(this, GEvent.CHANGED);
      }
    }
  }

  // Enable polymorphism.
  protected void keyPressedProcess(int keyCode, char keyChar, boolean shiftDown, boolean ctrlDown) { }

  protected void keyTypedProcess(int keyCode, char keyChar, boolean shiftDown, boolean ctrlDown){ }


  // Only executed if text has changed
  protected void changeText(){
    TextLayoutInfo tli;
    TextHitInfo thi = null, thiRight = null;

    pos += adjust;
    // Force layouts to be updated
    stext.getLines(buffer.g2);

    // Try to get text layout info for the current position
    tli = stext.getTLIforCharNo(pos);
    if(tli == null){
      // If unable to get a layout for pos then reset everything
      endTLHI = null;
      startTLHI = null;
      ptx = pty = 0;
      caretX = caretY = 0;
    }
    else {
      int posInLine = pos - tli.startCharIndex;

      // Get some hit info so we can see what is happening
      try{
        thiRight = tli.layout.getNextRightHit(posInLine);
      }
      catch(Exception excp){
        thiRight = null;
      }

      if(posInLine <= 0){          // At start of line
        thi = tli.layout.getNextLeftHit(thiRight);       
      }
      else if(posInLine >= tli.nbrChars){  // End of line
        thi = tli.layout.getNextRightHit(tli.nbrChars - 1)
      }
      else {                // Character in line;
        thi = tli.layout.getNextLeftHit(thiRight)
      }

      endTLHI.setInfo(tli, thi);
      // Cursor at end of paragraph graphic
      calculateCaretPos(endTLHI);

      // Is do we have to move cursor to start of next line
      if(newline) {    ///stext.getWrapWidth() != Integer.MAX_VALUE && caretX > stext.getWrapWidth()){
        if(pos >= stext.length()){
          stext.insertCharacters(pos, " ");
          stext.getLines(buffer.g2);
        }
        moveCaretRight(endTLHI);
        calculateCaretPos(endTLHI);
      }
      // Finish off by ensuring no selection, invalidate buffer etc.
      startTLHI.copyFrom(endTLHI);
    }
    bufferInvalid = true;
  }

  /**
   * Do not call this directly. A timer calls this method as and when required.
   */
  public void flashCaret(GTimer timer){
    showCaret = !showCaret;
  }

  /**
   * Do not call this method directly, G4P uses it to handle input from
   * the horizontal scrollbar.
   */
  public void hsbEventHandler(GScrollbar scrollbar, GEvent event){
    keepCursorInView = false;
    ptx = hsb.getValue() * (stext.getMaxLineLength() + 4);
    bufferInvalid = true;
  }

  /**
   * Do not call this method directly, G4P uses it to handle input from
   * the vertical scrollbar.
   */
  public void vsbEventHandler(GScrollbar scrollbar, GEvent event){
    keepCursorInView = false;
    pty = vsb.getValue() * (stext.getTextAreaHeight() + 1.5f * stext.getMaxLineHeight());
    bufferInvalid = true;
  }

  /**
   * Permanently dispose of this control.
   */
  public void markForDisposal(){
    if(tabManager != null)
      tabManager.removeControl(this);
    super.markForDisposal();
  }

  /**
   * Save the styled text used by this control to file. <br>
   * It will also save the start and end position of any text selection.
   *
   * @param fname the name of the file to use
   * @return true if saved successfully else false
   */
  public boolean saveText(String fname){
    if(stext == null)
      return false;
    if(hasSelection()){
      stext.startIdx = startTLHI.tli.startCharIndex + startTLHI.thi.getInsertionIndex();
      stext.endIdx = endTLHI.tli.startCharIndex + endTLHI.thi.getInsertionIndex();
    }
    else {
      stext.startIdx = stext.endIdx = -1;
    }
    StyledString.save(winApp, stext, fname);
    return true;
  }

  /**
   * Load the styled string to be used by this control. <br>
   * It will also restore any text selection saved with the text.
   *
   * @param fname the name of the file to use
   * @return true if loaded successfully else false
   */
  public boolean loadText(String fname){
    StyledString ss = StyledString.load(winApp, fname);
    if(ss == null)
      return false;
    setStyledText(ss);
    // Now restore any text selection
    if(stext.startIdx >=0){ // we have a selection
      // Selection starts at ...
      startTLHI = new TextLayoutHitInfo();
      startTLHI.tli = stext.getTLIforCharNo(stext.startIdx);
      int pInLayout = stext.startIdx - startTLHI.tli.startCharIndex;
      if(pInLayout == 0)
        startTLHI.thi = startTLHI.tli.layout.getNextLeftHit(1);
      else
        startTLHI.thi = startTLHI.tli.layout.getNextRightHit(pInLayout - 1);
      // Selection ends at ...
      endTLHI = new TextLayoutHitInfo();
      endTLHI.tli = stext.getTLIforCharNo(stext.endIdx);
      pInLayout = stext.endIdx - endTLHI.tli.startCharIndex;

      if(pInLayout == 0)
        endTLHI.thi = endTLHI.tli.layout.getNextLeftHit(1);
      else
        endTLHI.thi = endTLHI.tli.layout.getNextRightHit(pInLayout - 1);
      calculateCaretPos(endTLHI);
    }
    bufferInvalid = true;
    return true;
  }

  /**
   * Setting the styled text depends on whether this is a GTextField
   * or GTextArea object. Either way the stext will be valid after
   * this call so we can restore selection.
   *
   * @param ss
   */
  public abstract void setStyledText(StyledString ss);

}
TOP

Related Classes of g4p_controls.GEditableTextControl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.